
/*
 * En este archivo, implementaremos las funciones definidas
 * en el archivo vectores.h
 */

#define _FILE_OFFSET_BITS 64 // Necesario para que funcione fseeko

#include <stdlib.h>
#include <stdio.h>
#include <malloc.h>
#include <string.h>
#include <math.h>
#include <limits.h>
#include <sys/time.h>
#include <sys/stat.h>
#include "vectores.h"


char *crearNombreArchivo();
long long *dividirTamEn2(long long dimension);
int elementoEnBuffer(struct VECTOR *vector, long long posicion);
int cargarBufferParaPosicion(struct VECTOR *vector, long long posicion);
int obtenerDeBuffer(struct VECTOR *vector, long long posicion, int nroBuffer);
void escribirEnBuffer(struct VECTOR *vector, long long posicion, int valor, int nroBuffer);


int directorioCreado = 0;


/*
 * Implementación de la función definida en cabecera.
 */
struct VECTOR *crearVector(long long dimension) {

    // Tratamos de crear el directorio si es necesario
    if (!directorioCreado) {
        mkdir("/tmp/cp/", S_IRWXU | S_IRGRP | S_IROTH);
        mkdir("/tmp/cp/vect/", S_IRWXU | S_IRGRP | S_IROTH);
        directorioCreado = 1;
    }
    
    // No existen vectores de dimension 0 o negativa!
    if (dimension<1) {
        return NULL;
    }

    // Creamos el objeto que devolveremos y lo devolvemos
    struct VECTOR *vector = malloc(sizeof(struct VECTOR));

    // Obtenemos un nombre de archivo para el soporte en disco del vector
    vector->nombreArchivo = crearNombreArchivo();
    
    // Creamos el archivo de soporte y lo asignamos
    vector->archivoEnDisco = fopen(vector->nombreArchivo, "w+");
    
    // Configuramos el resto de los atributos
    vector->desplazamientoVirtual = 0;
    vector->dimensionReal = dimension;
    vector->dimensionVirtual = dimension;
    vector->original = 1;
    
    // Inicializamos los buffers
    vector->buffers = malloc(sizeof(struct BUFFERS));
    int i;
    for (i=0; i<MAX_CANT_BUFFERS; i++) {
        vector->buffers->datos[i] = malloc(sizeof(int)*TAM_BUFFER);
        vector->buffers->enUso[i] = 0;
    }
    
    return vector;

}


/*
 * Implementación de la función definida en cabecera.
 */
void eliminarVector(struct VECTOR *vector) {
    
    if (vector->original) {
    
        // Cerramos y eliminamos el archivo de soporte
        fclose(vector->archivoEnDisco);
        remove(vector->nombreArchivo);

        // Liberamos la memoria ocupada
        free(vector->nombreArchivo);
        
        // Liberamos los buffers
        int p;
        for (p=0; p<MAX_CANT_BUFFERS; p++) {
            free(vector->buffers->datos[p]);
        }
        free(vector->buffers);
        
    }
    
    free(vector);
    
}


/*
 * Implementación de la función definida en cabecera.
 */
struct VECTOR **dividirVectorEnDisco(struct VECTOR *vector) {
    
    // Calculamos los tamaños de las partes
    long long *tams = dividirTamEn2(vector->dimensionVirtual);
    
    // Creamos el array de vectores
    struct VECTOR **vectores = malloc(sizeof(struct VECTOR *) * 2);
    
    // Creamos el primer vector a partir del vector original
    vectores[0] = malloc(sizeof(struct VECTOR));
    vectores[0]->archivoEnDisco = vector->archivoEnDisco;
    vectores[0]->dimensionReal = vector->dimensionReal;
    vectores[0]->nombreArchivo = vector->nombreArchivo;
    vectores[0]->original = 0;
    vectores[0]->desplazamientoVirtual = vector->desplazamientoVirtual;
    vectores[0]->dimensionVirtual = tams[0];
    vectores[0]->buffers = vector->buffers;
    
    // Creamos el segundo vector a partir del vector original
    vectores[1] = malloc(sizeof(struct VECTOR));
    vectores[1]->archivoEnDisco = vector->archivoEnDisco;
    vectores[1]->dimensionReal = vector->dimensionReal;
    vectores[1]->nombreArchivo = vector->nombreArchivo;
    vectores[1]->original = 0;
    vectores[1]->desplazamientoVirtual = vector->desplazamientoVirtual + tams[0];
    vectores[1]->dimensionVirtual = tams[1];
    vectores[1]->buffers = vector->buffers;
    
    // Liberamos memoria
    free(tams);
    
    return vectores;

}


/*
 * Implementación de la función definida en cabecera.
 */
void escribirValor(struct VECTOR *vector, int valor, long long posicion) {
    
    int nroBuffer = elementoEnBuffer(vector, posicion);
    
    if (nroBuffer == -1) {
        nroBuffer = cargarBufferParaPosicion(vector, posicion);
    }
    
    escribirEnBuffer(vector, posicion, valor, nroBuffer);
    
}


/*
 * Implementación de la función definida en cabecera.
 */
int obtenerValor(struct VECTOR *vector, long long posicion) {

    int nroBuffer = elementoEnBuffer(vector, posicion);
    
    if (nroBuffer==-1) {
        nroBuffer = cargarBufferParaPosicion(vector, posicion);
    }
    
    return obtenerDeBuffer(vector, posicion, nroBuffer);
    
}


/*
 * Implementación de la función definida en cabecera.
 */
long long obtenerDimension(struct VECTOR *vector) {
    return vector->dimensionVirtual;
}


/*
 * Implementación de la función definida en cabecera.
 */
void copiarContenidoDeVector(struct VECTOR *origen, struct VECTOR *destino) {
    long long dimension = obtenerDimension(origen);
    long long i;
    for (i=0; i<dimension; i++) {
        long long valor = obtenerValor(origen, i);
        escribirValor(destino, valor, i);
    }
}


/*
 * Crea un timestamp en una cadena y lo retorna
 */
char *crearNombreArchivo() {
    char *nombre = malloc(sizeof(char) * 50);
    struct timeval tv;
    gettimeofday(&tv,NULL);
    sprintf(nombre, "/tmp/cp/vect/%lld%lld", (long long int)tv.tv_sec, (long long int)tv.tv_usec);
    return nombre;
}


/*
 * Divide la dimension pasada por parámetro en 2 partes iguales
 */
long long *dividirTamEn2(long long dimension) {
    long long *tams = malloc(sizeof(long long) * 2);
    tams[0] = floor(dimension/2L);
    tams[1] = dimension-tams[0];
    return tams;
}


int elementoEnBuffer(struct VECTOR *vector, long long posicion) {
    
    long long posicionReal = vector->desplazamientoVirtual + posicion;
    
    long p;
    for (p=0; p<MAX_CANT_BUFFERS; p++) {
        if (vector->buffers->enUso[p]) {
            long long offsetInicio = vector->buffers->offsetInicio[p];
            long long offsetFin = offsetInicio + vector->buffers->tamanio[p];
            if (posicionReal>=offsetInicio && posicionReal<offsetFin) {
                return p;
            }
        }
    }
    
    return -1;
    
}


int cargarBufferParaPosicion(struct VECTOR *vector, long long posicion) {
    
    // Obtenemos un buffer sin uso
    int nroBuff = -1;
    int u;
    for (u=0; u<MAX_CANT_BUFFERS; u++) {
        if (!vector->buffers->enUso[u]) {
            nroBuff = u;
            break;
        }
    }
    
    // Si no se encontro un buffer sin uso, buscamos uno cuyo ultimo acceso sea el mas viejo
    if (nroBuff==-1) {
        
        time_t tiempo = LONG_MAX;
        int indiceUltimoTiempo = -1;
        
        int k;
        for (k=0; k<MAX_CANT_BUFFERS; k++) {
            if (tiempo > vector->buffers->ultimoAcceso[k]) {
                tiempo = vector->buffers->ultimoAcceso[k];
                indiceUltimoTiempo = k;
            }
        }
        
        nroBuff = indiceUltimoTiempo;
        
        // Escribimos al disco todos los elementos del buffer a liberar, si ha sido modificado
        if (vector->buffers->modificado) {
            off_t posicionEnArchivo = vector->buffers->offsetInicio[nroBuff];
            fseeko(vector->archivoEnDisco, posicionEnArchivo, SEEK_SET);
            fwrite(vector->buffers->datos[nroBuff], sizeof(int), vector->buffers->tamanio[nroBuff], vector->archivoEnDisco);
        }
        
    } else {
        vector->buffers->enUso[nroBuff] = 1;
    }
    
    // Obtenemos el nuevo offset del buffer en cuestion
    long long posicionReal = vector->desplazamientoVirtual + posicion;
    long long offset = posicionReal / TAM_BUFFER;
    vector->buffers->offsetInicio[nroBuff] = offset * TAM_BUFFER;
    
    // Obtenemos el nuevo tamaño del buffer en cuestion
    long long tamBuffer;
    if (offset+TAM_BUFFER >= vector->dimensionReal) {
        tamBuffer = vector->dimensionReal-offset;
    } else {
        tamBuffer = TAM_BUFFER;
    }
    vector->buffers->tamanio[nroBuff] = tamBuffer;
    
    vector->buffers->modificado[nroBuff] = 0;
    
    // Leemos los datos del disco y los escribimos en el buffer
    off_t posicionEnArchivo = sizeof(int) * (offset);
    fseeko(vector->archivoEnDisco, posicionEnArchivo, SEEK_SET);
    int dummy = fread(vector->buffers->datos[nroBuff], sizeof(int), tamBuffer, vector->archivoEnDisco);
    if (dummy != sizeof(char)) {
        // TODO - logear
    }
    
    // Retornamos el valor leido
    return nroBuff;
    
}


int obtenerDeBuffer(struct VECTOR *vector, long long posicion, int nroBuffer) {
    
    long long posicionReal = vector->desplazamientoVirtual + posicion;
    long long offsetInicio = vector->buffers->offsetInicio[nroBuffer];
    long long posicionEnBuffer = posicionReal - offsetInicio;
    
    struct timeval time;
    gettimeofday(&time, NULL);
    vector->buffers->ultimoAcceso[nroBuffer] = time.tv_usec;
    
    return vector->buffers->datos[nroBuffer][posicionEnBuffer];
    
}


void escribirEnBuffer(struct VECTOR *vector, long long posicion, int valor, int nroBuffer) {
    
    long long posicionReal = vector->desplazamientoVirtual + posicion;
    long long offsetInicio = vector->buffers->offsetInicio[nroBuffer];
    long long posicionEnBuffer = posicionReal - offsetInicio;
    
    vector->buffers->datos[nroBuffer][posicionEnBuffer] = valor;
    
    vector->buffers->modificado[nroBuffer] = 1;
    
    struct timeval time;
    gettimeofday(&time, NULL);
    vector->buffers->ultimoAcceso[nroBuffer] = time.tv_usec;
    
}
